Skip to content

feat(sdk-core): getMpcV2RetrofitDataFromMpcV1Keys for EdDSA - #9461

Open
bitgo-ai-agent-dev[bot] wants to merge 2 commits into
masterfrom
WCI-1263-getMpcV2RetrofitDataFromMpcV1Key-EdDSA
Open

feat(sdk-core): getMpcV2RetrofitDataFromMpcV1Keys for EdDSA#9461
bitgo-ai-agent-dev[bot] wants to merge 2 commits into
masterfrom
WCI-1263-getMpcV2RetrofitDataFromMpcV1Key-EdDSA

Conversation

@bitgo-ai-agent-dev

Copy link
Copy Markdown
Contributor

What

  • Add EddsaRetrofitData type (s_i_0, expectedPk, chainCode) exported from @bitgo/sdk-core
  • Add public instance method getMpcV2RetrofitDataFromMpcV1Keys on EddsaMPCv2Utils — takes both user and backup MPCv1 key share strings, returns a pair of EddsaRetrofitData objects
  • Add private static helper getMpcV2RetrofitDataFromMpcV1Key — parses a single MPCv1 JSON key share, derives the Ed25519 scalar via RFC 8032 SHA-512 key expansion + clamping, and returns the retrofit data struct
  • Input validation: seed must be exactly 64 hex chars (32 bytes), chaincode must be exactly 64 hex chars, pShare.y must be a non-empty string
  • Zeroize sensitive Buffers (seed bytes, full SHA-512 digest, clamped scalar) in a try/finally block after the hex string is captured

Why

Required by WCI-1264 (getUserAndBackupSession + createKeychains retrofit wiring). The retrofit DKG ceremony (ed25519_dkg_round0_import) needs EddsaRetrofitData seeded from the user's existing MPCv1 keys before the ceremony begins. MPCv1 stores a 32-byte seed rather than the scalar directly; this method performs the RFC 8032 key expansion so both user and backup parties can seed their DKG sessions from their existing KeyCard material, enabling in-place migration from MPCv1 to MPCv2 without re-keying.

Test plan

  • 11 unit tests cover: matching expectedPk, correct chainCode per party, clamped scalar value, bit-level clamping verification, missing pShare.y, empty pShare.y, missing/short uShare.seed, missing uShare.chaincode on user and backup

Ticket: WCI-1263

Add `getMpcV2RetrofitDataFromMpcV1Keys` (public instance method) and
a private static helper `getMpcV2RetrofitDataFromMpcV1Key` on
`EddsaMPCv2Utils`. Also introduce the `EddsaRetrofitData` type and
export it from the package root.

The methods convert MPCv1 EdDSA key shares (JSON with uShare.seed
and uShare.chaincode) into `EddsaRetrofitData` objects that can be
passed to `new EddsaMPSDkg.DKG(…, retrofitData)` to seed a retrofit
DKG ceremony.

Scalar derivation follows RFC 8032 Ed25519 key expansion:
  scalar = clamp(SHA512(seed_bytes)[0:32])
where clamping clears bits 0-2 of byte[0], clears bit 7 of byte[31],
and sets bit 6 of byte[31].

The aggregate public key (expectedPk) is read from the user key's
pShare.y and reused for both parties, since both parties share the
same root public key in the MPCv1 scheme.

Required by WCI-1264 (getUserAndBackupSession + createKeychains
retrofit wiring) to derive the retrofit data before starting the DKG
ceremony.

Ticket: WCI-1263
Session-Id: 9430179b-3b09-4c4c-9c75-723c484f16b5
Task-Id: 29bdbf4d-6e4f-4646-bd79-06a89e5dcf28
@linear-code

linear-code Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

WCI-1263

@ralph-bitgo
ralph-bitgo Bot force-pushed the WCI-1263-getMpcV2RetrofitDataFromMpcV1Key-EdDSA branch from decc117 to 38ea35e Compare August 10, 2026 16:18
@bitgo-ai-agent-dev
bitgo-ai-agent-dev Bot force-pushed the WCI-1263-getMpcV2RetrofitDataFromMpcV1Key-EdDSA branch from 38ea35e to 4dfa8d0 Compare August 10, 2026 16:18
@Marzooqa
Marzooqa force-pushed the WCI-1263-getMpcV2RetrofitDataFromMpcV1Key-EdDSA branch 2 times, most recently from 18b0093 to 62d3e34 Compare August 11, 2026 12:26
Replaces the hand-rolled scalar derivation (manual SHA-512 + clamp
with a zeroize try/finally, and reading a non-existent `pShare` field
off the raw decrypted keycard JSON) with a call to
`MPC.keyCombine(uShare, yShares)` — the same combine step every other
MPCv1 EdDSA code path already uses. The decrypted MPCv1 key share
(`SigningMaterial`) only ever contains `uShare` + `bitgoYShare` +
`backupYShare`/`userYShare`; it has no `pShare`, and `uShare.chaincode`
is only one party's additive contribution to the real BIP32 chain
code, not the combined value. Deriving `s_i_0`/`expectedPk`/`chainCode`
from the real `pShare.u`/`pShare.y`/`pShare.chaincode` output of
keyCombine fixes both issues at once and asserts user/backup agree on
the aggregate public key and chain code before returning.

Structures the per-party helper like ecdsaMPCv2.ts's
getMpcV2RetrofitDataFromMpcV1Key: it takes the raw JSON key-share
string and an MPCv2PartiesEnum.USER/BACKUP index, JSON.parses and
switches on the index internally, and asserts every share it's about
to feed into keyCombine (uShare, bitgoYShare, and the counterparty
YShare) right where they're used — mirroring ECDSA's assertion style
since JSON.parse returns unchecked data and the SigningMaterial type's
non-optional fields aren't a runtime guarantee.

Moves `EddsaRetrofitData` into `@bitgo/sdk-lib-mpc`'s
`tss/eddsa-mps/types.ts` (as `MPSTypes.EddsaRetrofitData`), mirroring
where DKLS keeps its `RetrofitData` type, instead of defining and
re-exporting it from sdk-core.

Rewrites tests to exercise real 3-party MPCv1 key shares generated via
`Eddsa.keyShare`/`keyCombine` instead of hand-built JSON fixtures with
a `pShare` field that never occurs in production data, and covers each
assertion branch.

Ticket: WCI-1263
@Marzooqa
Marzooqa force-pushed the WCI-1263-getMpcV2RetrofitDataFromMpcV1Key-EdDSA branch from 62d3e34 to 9957029 Compare August 11, 2026 13:09
@Marzooqa

Copy link
Copy Markdown
Contributor

@claude

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Claude finished @Marzooqa's task —— View job


Code Review Complete ✅

Reviewing the EdDSA MPCv2 retrofit implementation for WCI-1263.

Todo List:

  • Review EddsaRetrofitData type definition
  • Analyze getMpcV2RetrofitDataFromMpcV1Keys implementation
  • Review private helper getMpcV2RetrofitDataFromMpcV1Key
  • Check unit tests coverage and quality
  • Verify RFC 8032 compliance and security practices
  • Provide comprehensive feedback

Review Summary

This PR successfully implements EdDSA MPCv2 retrofit functionality for migrating from MPCv1 to MPCv2 keys. The implementation is solid and addresses the requirements well.

Key Strengths

1. Correct Architectural Approach

  • Properly uses MPC.keyCombine() instead of manual scalar derivation (fixed from earlier version)
  • Correctly derives retrofit data from pShare.u/pShare.y/pShare.chaincode output
  • Validates that user and backup keys produce consistent aggregate public key and chain code

2. Type Safety & Location

  • EddsaRetrofitData type is well-defined with clear documentation
  • Moved to @bitgo/sdk-lib-mpc/src/tss/eddsa-mps/types.ts for better organization (mirrors ECDSA pattern)
  • Type structure matches expected MPS DKG ceremony requirements

3. Robust Input Validation

  • Comprehensive assertions for all required MPCv1 key material fields (uShare, bitgoYShare, party-specific YShares)
  • Clear error messages for missing fields distinguish between user and backup context
  • JSON.parse error handling for malformed input

4. Excellent Test Coverage

  • 11 unit tests covering all critical paths and edge cases
  • Tests use real MPCv1 key shares generated via MPC.keyShare() rather than fixtures
  • Edge case coverage: missing fields, empty strings, wrong party configurations
  • Validation tests confirm user/backup keys produce matching expectedPk and chainCode

5. Security Considerations

  • No longer manipulates raw seed bytes or performs manual SHA-512/clamping
  • Delegates cryptographic operations to the existing, battle-tested MPC.keyCombine() logic
  • Maintains the same security model as existing MPCv1 EdDSA operations

Implementation Details

Public Interface (lines 1073-1099)

async getMpcV2RetrofitDataFromMpcV1Keys(params: { 
  mpcv1UserKeyShare: string; 
  mpcv1BackupKeyShare: string 
}): Promise<{
  userRetrofitData: MPSTypes.EddsaRetrofitData;
  backupRetrofitData: MPSTypes.EddsaRetrofitData;
}>
  • Clean async API accepting raw MPCv1 key share strings
  • Returns structured retrofit data for both parties
  • Cross-validates that both parties agree on aggregate key material

Private Helper (lines 1101-1135)

private static getMpcV2RetrofitDataFromMpcV1Key(
  mpcv1PartyKeyShare: string,
  mpcv1PartyIndex: MPCv2PartiesEnum.USER | MPCv2PartiesEnum.BACKUP,
  mpc: Awaited<ReturnType<typeof getInitializedMpcInstance>>
): MPSTypes.EddsaRetrofitData
  • Proper separation of concerns - handles single party processing
  • Uses MPCv2PartiesEnum for type-safe party identification
  • Mirrors pattern from existing ECDSA implementation

Minor Suggestions

  1. Error Message Consistency: Consider standardizing error message format across all validation checks
  2. Performance: The MPC instance initialization could potentially be cached if this becomes a hot path
  3. Documentation: Consider adding JSDoc examples for the expected MPCv1 key share JSON structure

Test Quality Assessment ⭐⭐⭐⭐⭐

The test suite is exceptionally thorough:

  • Positive cases: Real key generation and successful retrofit
  • Input validation: Missing fields, malformed JSON, type mismatches
  • Cross-validation: Ensures user/backup consistency
  • Integration: Uses real MPC library calls instead of mocks where appropriate

Security Assessment ✅

  • No sensitive data exposure: Operates on existing MPCv1 key material without exposing additional secrets
  • Cryptographic correctness: Uses the same keyCombine logic as production EdDSA flows
  • Input sanitization: Proper validation before passing data to cryptographic functions

Conclusion

This is a well-implemented feature that successfully enables MPCv1 to MPCv2 migration for EdDSA keys. The code is secure, well-tested, and follows BitGoJS architectural patterns. The fix to use real keyCombine() instead of manual derivation significantly improves correctness and maintainability.

Recommendation: ✅ Approve


Job: PR Comment | Branch: WCI-1263-getMpcV2RetrofitDataFromMpcV1Key-EdDSA

@Marzooqa
Marzooqa marked this pull request as ready for review August 11, 2026 15:00
@Marzooqa
Marzooqa requested review from a team as code owners August 11, 2026 15:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant